/* 
 * File:   compresor.cpp
 * Author: ariel
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <arpa/inet.h>

#include "compresor_logic.h"
#include "lz77_logic.h"
#include "../archivos.h"

unsigned int buscarCoincidencia_2(char *ventana, char *buffer, unsigned int *posicion, int tam_ventana, int longitud_max_match) {
  /* TODO: Hacer las mejoras correspondientes aca! */
  int longitud, maxLongitud, i, j;

  maxLongitud = 0;

  for (i = 0; i < tam_ventana; i++) {
    longitud = 0;

    for (j = 0; j < tam_ventana; j++) {
      if (ventana[i + j] == buffer[j]) {
        longitud++;
      } else {
        break;
      }

      if (longitud > maxLongitud) {
        *posicion = i;
        maxLongitud = longitud;
      }

      if (maxLongitud == longitud_max_match) {
        break;
      }
    }
  }

  return maxLongitud;
}

void desplazarVentana_2(char *ventana, int cantidad, int tam_ventana) {
  memcpy(ventana, ventana + cantidad, 2 * tam_ventana - cantidad);
}

void emitir_2(BufferProceso_2 *buf, int valor, int numeroDeBits, FILE *archivo) {
  unsigned int bitsADesplazar, bytesAEscribir;
  uint32_t datoAGuardar;
  
  buf->buffer <<= numeroDeBits;
  buf->bitsOcupados += numeroDeBits;
  buf->buffer |= valor;

  bytesAEscribir = buf->bitsOcupados / 8;

  if (bytesAEscribir > 0) {
    bitsADesplazar = 8 * sizeof (buf->buffer) - buf->bitsOcupados;
    buf->buffer <<= bitsADesplazar;

    datoAGuardar = htonl(buf->buffer);
    fwrite((char*)(&datoAGuardar), 1, bytesAEscribir, archivo);
    buf->bitsOcupados -= bytesAEscribir * 8;
    buf->buffer >>= bitsADesplazar;
  }
}

void emitirRelleno_2(BufferProceso_2 *buf, FILE *archivo) {
  unsigned int bitsARellenar, relleno;

  bitsARellenar = 8 - buf->bitsOcupados;

  if (bitsARellenar == 0) {
    bitsARellenar = 8;
  }

  relleno = 1;
  relleno <<= (bitsARellenar - 1);

  emitir_2(buf, relleno, bitsARellenar, archivo);
}

void emitirReferencia_2(int posicion, int longitud, BufferProceso_2 *buf, FILE *archivo) {
  unsigned int temp;

  temp = 1 << (BITS_LONGITUD + BITS_POSICION);
  temp |= (longitud << BITS_POSICION);
  temp |= posicion;

  emitir_2(buf, temp, (BITS_LONGITUD + BITS_POSICION + 1), archivo);
}

void emitirCaracter_2(unsigned char caracter, BufferProceso_2 *buf, FILE *archivo) {
  emitir_2(buf, (unsigned int) caracter, 9, archivo);
}

int comprimirLZ77_logic(const char *nombreArchivo, const char* nombreArchivoSalida) {
  unsigned int posicion, tamanio, procesado, longitud;
  char *ventana, *buffer;
  FILE *archivoEntrada, *archivoSalida;
  BufferProceso_2 bufferSalida;

  if ((archivoEntrada = fopen(nombreArchivo, "rb")) == NULL) {
    return ERR_ARCHIVO_NO_EXISTE;
  }

  archivoSalida = fopen(nombreArchivoSalida, "wb");

  cout<<"Parametros modificables de compresion"<<endl;
  vector<int> valores_config = ConfigLZ::cargarConfiguracionLZ77();
  int tam_ventana = valores_config[0];
  int longitud_min_match = valores_config[1];
  int longitud_max_match = valores_config[2];

  cout<<"tam_ventana: "<<tam_ventana<<endl;
  cout<<"long_min_match: "<<longitud_min_match<<endl;
  cout<<"long_max_match: "<<longitud_max_match<<endl;

  ventana = (char*)malloc(tam_ventana * 2);
  memset(ventana, 0, tam_ventana * 2);
  buffer = ventana + tam_ventana;

  fread(buffer, 1, tam_ventana, archivoEntrada);

  tamanio = obtenerTamanio(archivoEntrada);
  procesado = 0;

  /* TODO: PASAR A UN TDA! ESTO ESTÁ MAL! */
  bufferSalida.bitsOcupados = 0;
  bufferSalida.buffer = 0;

  while (procesado < tamanio) {
    longitud = buscarCoincidencia_2(ventana, buffer, &posicion, tam_ventana, longitud_max_match);

    if (longitud >= longitud_min_match) {
      if (procesado + longitud > tamanio) {
        emitirReferencia_2(posicion, tamanio - procesado, &bufferSalida, archivoSalida);
      } else {
        emitirReferencia_2(posicion, longitud, &bufferSalida, archivoSalida);
      }
    } else {
      emitirCaracter_2(buffer[0], &bufferSalida, archivoSalida);
      longitud = 1;
    }

    desplazarVentana_2(ventana, longitud, tam_ventana);
    fread(ventana + tam_ventana * 2 - longitud, 1, longitud, archivoEntrada);
    procesado += longitud;
  }

  emitirRelleno_2(&bufferSalida, archivoSalida);

  /* Limpieza de recursos */
  fclose(archivoEntrada);
  fclose(archivoSalida);
  free(ventana);

  return EXIT_SUCCESS;
}
